package service

import (
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"gitee.com/InsignificantDust/msrv/files"
)

type htmlService struct {
	// 服务名称
	srvName string
	// cmdType
	srvLang string
	// 命令参数
	cmdArgs string
	// 完整包名,不含路径
	pkgName string
	// 工作目录
	workDir string
	// 服务包目录
	pkgDir string
	// 源包目录
	pkgSourceDir string
	// 备份目录
	backupDir string
	// 源配置文件目录
	srcConfigDir string
	// 配置文件目录
	configDir string
	// PidFile
	pidFile string
	// 健康检查url
	healthUrl string
	// 服务启动程序命令
	runCmd string
	// 服务启动超时时间
	startTimeout int
	// 文件后缀
	pkgPostfix string
	// 备份后缀
	bakPostfix string
	// 包来源
	pkgFrom string
	// 如果需要下载时请提供下载url, 从本地为空字符串
	pkgUrl string
	// 服务描述信息
	description string
}

func (j *htmlService) SrvName() string {
	return j.srvName
}

func (j *htmlService) Start() error {
	fmt.Printf("启动服务%s\n", j.srvName)
	fmt.Printf("执行服务检查\n")
	if j.Status() {
		fmt.Printf("服务%s已运行\n", j.srvName)
		return nil
	}
	if !j.Validate() {
		return NewServiceError(j.srvName, "执行服务检查失败：非法文件格式")
	}
	htmlCmd, _ := j.GetCmd()
	if htmlCmd == "" {
		return NewServiceError(j.srvName, "执行服务检查失败：服务启动命令"+j.runCmd+"不存在")
	}
	if !files.IsDirExist(j.workDir) {
		return NewServiceError(j.srvName, "执行服务检查失败：服务软件包不存在")
	}
	var args []string
	args = append(args, htmlCmd)
	attr := &os.ProcAttr{Dir: j.workDir, Env: os.Environ(), Files: []*os.File{nil, os.Stdout, os.Stderr}}
	process, err := os.StartProcess(htmlCmd, args, attr)
	if err != nil {
		fmt.Println(err)
		return err
	}

	chkCounter := j.startTimeout

	for {
		time.Sleep(chkInterval * time.Second)
		if chkCounter < 0 {
			process.Kill()
			return NewServiceError(j.srvName, "服务启动失败", "服务启动超时！")
		}
		if j.Status() {
			break
		}
		chkCounter = chkCounter - 5
	}
	fmt.Printf("服务%s启动成功\n", j.srvName)
	return nil
}

func (j *htmlService) Stop() error {
	fmt.Printf("关闭服务%s\n", j.srvName)
	if !j.Status() {
		return nil
	}
	htmlCmd, _ := j.GetCmd()
	if htmlCmd == "" {
		return NewServiceError(j.srvName, "服务运行命令"+j.runCmd+"不存在")
	}
	var args []string
	args = append(args, htmlCmd)
	args = append(args, "-s")
	args = append(args, "stop")
	attr := &os.ProcAttr{Dir: j.workDir, Env: os.Environ(), Files: []*os.File{nil, os.Stdout, os.Stderr}}
	process, err := os.StartProcess(htmlCmd, args, attr)
	if err != nil {
		return err
	}
	processState, err := process.Wait()
	if processState.Exited() {
		return nil
	}
	return err
}

func (j *htmlService) Restart() error {
	if err := j.Stop(); err != nil {
		return err
	}
	if err := j.Start(); err != nil {
		return err
	}
	return nil
}

func (j *htmlService) Deploy() error {
	fmt.Printf("部署服务%s......\n", j.srvName)
	if !files.IsDirExist(j.workDir) {
		if err := files.Mkdir(j.workDir); err != nil {
			return NewServiceError(j.srvName, "部署失败：", err.Error())
		}
	}
	sourcePkg := filepath.Join(j.pkgSourceDir, j.pkgName)
	if files.IsNotExist(sourcePkg) {
		return NewServiceError(j.srvName, "部署失败：服务软件包不存在")
	}
	if files.IsNotExist(j.srcConfigDir) {
		return NewServiceError(j.srvName, "部署失败：源配置文件不存在")
	}
	if err := files.UnZip(sourcePkg, j.workDir); err != nil {
		return NewServiceError(j.srvName, "部署失败：", err.Error())
	}
	if j.configDir == "" {
		return NewServiceError(j.srvName, "部署失败：未找到配置文件，请根据要求配置")
	}
	if err := files.Copy(j.srcConfigDir, j.configDir, false); err != nil {
		if err = files.RemoveDir(j.workDir); err != nil {
			return NewServiceError(j.srvName, "部署失败：清理残留文件失败", err.Error())
		}
		return NewServiceError(j.srvName, "部署失败：", err.Error())
	}
	if err := j.Start(); err != nil {
		return NewServiceError(j.srvName, "部署失败：", err.Error())
	}
	fmt.Printf("服务%s部署完成\n", j.srvName)
	return nil
}

func (j *htmlService) Status() bool {
	if j.healthUrl == "" {
		return false
	}
	resp, err := http.Get(j.healthUrl)
	if err != nil {
		return false
	}
	if resp.StatusCode != 200 {
		return false
	}
	return true
}

func (j *htmlService) Rollback(updateConfig bool) error {
	fmt.Printf("服务%s回滚\n", j.srvName)

	bakPkg := filepath.Join(j.backupDir, filepath.Base(j.workDir)+".zip")
	baseConfig := filepath.Base(j.configDir)
	bakConfig := filepath.Join(j.backupDir, baseConfig)
	if files.IsNotExist(bakPkg) {
		return NewServiceError(j.srvName, "服务回滚失败：备份文件不存在")
	}
	if updateConfig {
		if !files.IsExist(bakConfig) {
			return NewServiceError(j.srvName, "服务回滚失败：未备份配置文件！")
		}
		files.CopyDir(bakConfig, j.configDir, true)
	}
	if files.IsExist(j.workDir) {
		files.RemoveDir(j.workDir)
	}
	if err := files.UnZip(bakPkg, filepath.Dir(j.workDir)); err != nil {
		return NewServiceError(j.srvName, "服务回滚失败：", err.Error())
	}
	if err := j.Restart(); err != nil {
		return err
	}
	return nil
}

func (j *htmlService) Update(updateConfig bool) error {
	fmt.Println("服务更新中......")
	sourcePkg := filepath.Join(j.pkgSourceDir, j.pkgName)
	if files.IsNotExist(sourcePkg) {
		error_info := fmt.Sprintf("更新失败：源服务软件包%s不存在\n", sourcePkg)
		return NewServiceError(j.srvName, error_info)
	}
	if err := j.backup(updateConfig); err != nil {
		return NewServiceError(j.srvName, "更新失败：", err.Error())
	}
	if err := j.Stop(); err != nil {
		return NewServiceError(j.srvName, "更新失败：", err.Error())
	}
	fmt.Println("执行更新")
	if updateConfig {
		err := files.Copy(j.srcConfigDir, j.configDir, true)
		if err != nil {
			return NewServiceError(j.srvName, "更新失败：", err.Error())
		}
	}
	if err := files.RemoveDir(j.workDir); err != nil {
		return NewServiceError(j.srvName, "更新失败", err.Error())
	}
	if err := files.Mkdir(j.workDir); err != nil {
		return NewServiceError(j.srvName, "更新失败", err.Error())
	}
	if err := files.UnZip(sourcePkg, j.workDir); err != nil {
		return NewServiceError(j.srvName, "更新失败：", err.Error())
	}
	if err := j.Start(); err != nil {
		return NewServiceError(j.srvName, "更新失败：", err.Error())
	}
	fmt.Println("服务更新完成")
	return nil
}

// 备份服务
func (j *htmlService) backup(updateConfig bool) error {
	baseWorkDir := filepath.Base(j.workDir)
	if !files.IsDirExist(j.workDir) {
		return NewServiceError(j.srvName, "更新失败：服务软件包不存在")
	}
	if !files.IsDirExist(j.backupDir) {
		fmt.Println("服务备份不目录不存在，创建备份目录")
		if err := files.Mkdir(j.backupDir); err != nil {
			return NewServiceError(j.srvName, "更新失败：", err.Error())
		}
	}
	if updateConfig {
		bakBaseConfig := filepath.Base(j.configDir)
		bakConfig := filepath.Join(j.backupDir, bakBaseConfig)
		if files.IsNotExist(j.configDir) {
			return NewServiceError(j.srvName, "备份失败，服务配置文件不存在")
		}
		if err := files.CopyDir(j.configDir, bakConfig, true); err != nil {
			return NewServiceError(j.srvName, "备份失败", err.Error())
		}
	}
	bakPkg := filepath.Join(j.backupDir, baseWorkDir+".zip")
	if files.IsDirExist(bakPkg) {
		fmt.Println("检测到旧的备份文件")
		if err := files.RemoveDir(bakPkg); err != nil {
			return NewServiceError(j.srvName, "更新失败：", err.Error())
		}
	}
	fmt.Println("备份当前版本")
	if err := files.Zip(bakPkg, j.workDir); err != nil {
		return NewServiceError(j.srvName, "更新失败：", err.Error())
	}
	return nil
}

func (j *htmlService) Uninstall() error {
	fmt.Println("卸载服务......")
	if err := j.Stop(); err != nil {
		return NewServiceError(j.srvName, "卸载失败：", err.Error())
	}
	if err := files.RemoveDir(j.workDir); err != nil {
		return NewServiceError(j.srvName, "卸载失败：", err.Error())
	}
	if files.IsExist(j.configDir) {
		files.RemoveDir(j.configDir)
	}
	return nil
}

func (j *htmlService) GetCmd() (string, error) {
	if filepath.IsAbs(j.runCmd) && files.IsExist(j.runCmd) && files.IsExecutable(j.runCmd) {
		return j.runCmd, nil
	}
	cmdPath, err := exec.LookPath(j.runCmd)
	if cmdPath != "" {
		return cmdPath, nil
	}
	return "", err
}

func (j *htmlService) Validate() bool {
	if j.pkgPostfix != ".zip" {
		return false
	}
	return true
}

func (j *htmlService) Describ() string {
	sourcePkg := filepath.Join(j.pkgSourceDir, j.pkgName)
	descStr := fmt.Sprintf("%s服务由%s编写而成，源码包为: %s,工作目录：%s ,配置目录：%s\n", j.srvName, j.srvLang, sourcePkg, j.workDir, j.configDir)
	return descStr
}
